# -*- coding:utf-8 -*-
import os
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = r'D:\conda\envs\cj\Library\plugins\platforms'
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QPushButton, QFrame, QLabel, QComboBox, 
                            QLineEdit, QMenuBar, QAction, QMenu,
                            QSizePolicy, QApplication, QGraphicsTextItem,
                            QGraphicsView, QMessageBox, QInputDialog)  # 添加 QMessageBox, QInputDialog

from PyQt5.QtChart import QChart, QChartView, QLineSeries, QValueAxis, QCategoryAxis
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from config import AppConfig
from data_processor import ProcessData
from controller import MainController
import sys


class MainUI(QMainWindow):
    # 在UI类的__init__方法中修改
    def __init__(self, config, processor, controller):
        super().__init__()
        self.config = config
        self.processor = processor
        self.controller = controller
        # 存储所有打开的图表窗口
        self.chart_dialogs = []
        # 存储matplotlib图形引用
        self.matplotlib_figures = []
        self.init_ui()
        
    def init_ui(self):
        """初始化用户界面组件"""
        # 主窗口设置
        self.setWindowTitle('皖江绘2.0')
        self.resize(800, 800)
        

        # 创建菜单栏
        self.create_menubar()
        
        # 主窗口中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局 - 垂直布局包含工具栏和绘图区
        main_layout = QVBoxLayout(central_widget)
        
        # 顶部工具栏
        self.setup_toolbar(main_layout)
        
        # 绘图区域
        self.setup_plot_area(main_layout)
        
        # 将下拉框引用传递给控制器
        self.controller.set_combo_box(self.section_combo)
        
        # 在所有UI组件初始化完成后，再设置信号连接
        self.setup_connections()  # 移动到最后执行

         
        # 在所有组件初始化后延迟最大化
        # from PyQt5.QtCore import QTimer
        # QTimer.singleShot(10, self.showMaximized)
        
    def create_menubar(self):
        """创建菜单栏并保存action引用"""
        menubar = self.menuBar()
        
        # 创建并保存所有action引用
        self.actions = {
            'import_action': menubar.addAction('导入单波束'),
            'import_multibeam_action': menubar.addAction('导入多波束'),
            
            'undo_action': menubar.addAction('撤销(Z)'),
            'modify_action': menubar.addAction('鼠标修改(M)'),
            'change_action': menubar.addAction('更改(C)'),
            'add_action': menubar.addAction('添加(A)'),
            'delete_action': menubar.addAction('删除(D)'),
            'original_plot_action': menubar.addAction('原画复现'),
            'prev_section_action': menubar.addAction('上一断面'),
            'next_section_action': menubar.addAction('下一断面'),
            'help_action': menubar.addAction('帮助'),
            'live_output_action': menubar.addAction('实况输出'),
            'raw_output_action': menubar.addAction('原始输出')
        }
        
    def setup_toolbar(self, main_layout):
        """创建顶部工具栏 - 优化美观版
        
        参数:
            main_layout: 主垂直布局对象，用于添加工具栏
            
        设计理念:
            1. 将工具栏分为三个主要区域: 
            左侧 - 断面选择 (40%宽度)
            中部 - 点过滤 (20%宽度)
            右侧 - 状态显示 (40%宽度)
            2. 使用弹性布局策略确保窗口缩放时保持比例
            3. 添加现代UI元素提升视觉体验
        """
        # ===== 1. 创建工具栏容器 =====
        toolbar = QFrame()
        # 设置工具栏样式 (CSS样式表)
        toolbar.setStyleSheet("""
            /* 工具栏整体样式 */
            QFrame {
                background-color: #f5f7fa;  /* 浅灰蓝色背景 */
                border-bottom: 1px solid #d1d8e0;  /* 底部浅灰色边框 */
                padding: 6px;  /* 内边距 */
            }
            /* 标签样式 */
            QLabel {
                color: #4a4a4a;  /* 深灰色文字 */
                font-weight: 500;  /* 中等字体粗细 */
            }
            /* 按钮样式 */
            QPushButton {
                background-color: #5b7cfa;  /* 主色调蓝色 */
                color: white;  /* 白色文字 */
                border-radius: 4px;  /* 圆角 */
                padding: 5px 12px;  /* 内边距: 上下5px, 左右12px */
                min-width: 60px;  /* 最小宽度 */
            }
            /* 按钮悬停效果 */
            QPushButton:hover {
                background-color: #4263eb;  /* 深蓝色悬停效果 */
            }
            /* 下拉框和输入框样式 */
            QComboBox, QLineEdit {
                border: 1px solid #d1d8e0;  /* 浅灰色边框 */
                border-radius: 4px;  /* 圆角 */
                padding: 5px;  /* 内边距 */
                background: white;  /* 白色背景 */
            }
        """)
        
        # ===== 2. 创建工具栏主布局 =====
        # 使用水平布局 (QHBoxLayout)
        toolbar_layout = QHBoxLayout(toolbar)
        
        # 设置布局参数:
        toolbar_layout.setSpacing(15)  # 控件间水平间距: 15像素
        toolbar_layout.setContentsMargins(10, 5, 10, 5)  # 布局内边距: 左10, 上5, 右10, 下5
        
        # ===== 3. 左侧区域: 断面选择组 =====
        # 创建容器组件
        section_group = QWidget()
        # 创建水平布局
        section_layout = QHBoxLayout(section_group)
        section_layout.setSpacing(8)  # 组内控件间距: 8像素
        section_layout.setContentsMargins(0, 0, 0, 0)  # 组内边距: 无
        
        # 断面选择标签
        section_label = QLabel("选择断面")
        
        # 断面下拉框
        self.section_combo = QComboBox()
        self.section_combo.setMinimumWidth(180)  # 最小宽度180px
        # 尺寸策略: 水平可扩展，垂直固定高度
        self.section_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        # 断面导航按钮
        self.prev_btn = QPushButton("◀ 上条")  # 改为实例属性
        self.prev_btn.setFixedWidth(80)
        self.next_btn = QPushButton("下条 ▶")  # 改为实例属性
        self.next_btn.setFixedWidth(80)
        
        
        # 将控件添加到断面组布局
        section_layout.addWidget(section_label)
        section_layout.addWidget(self.section_combo)
        section_layout.addWidget(self.prev_btn)  # 使用实例属性
        section_layout.addWidget(self.next_btn)  # 使用实例属性
        
        # ===== 右侧区域: 状态显示 =====
        self.status_label = QLabel("就绪 | 选择断面开始工作")
        self.status_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)  # 右对齐+垂直居中
        # 状态标签样式
        self.status_label.setStyleSheet("""
            color: #666;  /* 中灰色文字 */
            font-style: song;  /* 宋体 */
            padding-right: 10px;  /* 右侧内边距 */
        """)
        
        # 状态标签容器 (确保占据整个分配空间)
        status_container = QWidget()
        status_layout = QHBoxLayout(status_container)
        status_layout.addWidget(self.status_label)  # 将标签添加到容器
        
        # ===== 6. 将三个区域添加到工具栏 =====
        # 断面选择组 - 占40%宽度 (拉伸因子4)
        toolbar_layout.addWidget(section_group, 4)  
        
        # 添加固定间距 (20像素)
        toolbar_layout.addSpacing(20)  
        
        # 添加弹性空间 (推动状态组到最右侧)
        toolbar_layout.addStretch(1)  
        
        # 状态容器 - 占40%宽度 (拉伸因子4)
        toolbar_layout.addWidget(status_container, 4)  
        
        # ===== 7. 添加到主布局 =====
        # 在工具栏右侧添加汇总图按钮
        summary_buttons_layout = QHBoxLayout()
        summary_2d_btn = QPushButton("2D图")
        summary_3d_btn = QPushButton("3D图")
        
        summary_buttons_layout.addWidget(summary_2d_btn)
        summary_buttons_layout.addWidget(summary_3d_btn)
        
        # 将汇总按钮布局添加到工具栏布局
        toolbar_layout.addLayout(summary_buttons_layout)
        
        # 连接信号槽
        summary_2d_btn.clicked.connect(self.plot_all_sections_2d)
        summary_3d_btn.clicked.connect(self.plot_all_sections_3d)
        
        # 添加到主布局
        main_layout.addWidget(toolbar)
        
    def setup_plot_area(self, main_layout):
        """设置绘图区域 - 与原有matplotlib逻辑保持一致"""
        # 创建图表
        self.chart = QChart()
        self.chart.setTitle("未添加水下数据")
        self.chart.setAnimationOptions(QChart.NoAnimation)
        
        # 创建数据系列
        self.main_series = QLineSeries()  # 主数据线 (黑色)
        self.fix_series = QLineSeries()   # 修正线 (绿色)
        self.main_series.setName("原始数据")  # 添加系列名称作为图例文本
        self.fix_series.setName("修正数据")   # 添加系列名称作为图例文本
        self.main_series.setColor(Qt.black)
        self.fix_series.setColor(Qt.green)
        
        # 文本标注容器 - 新增
        self.text_annotations = []
        
        # 添加所有系列到图表 - 只添加主线和修正线
        self.chart.addSeries(self.main_series)
        self.chart.addSeries(self.fix_series)
        
        # 创建坐标轴
        self.axis_x = QValueAxis()
        self.axis_x.setTitleText("时间")
        self.axis_y = QValueAxis()
        self.axis_y.setTitleText("水深 (m)")
        
        self.chart.addAxis(self.axis_x, Qt.AlignBottom)
        self.chart.addAxis(self.axis_y, Qt.AlignLeft)
        
        # 附加系列到坐标轴 - 只附加主线和修正线
        self.main_series.attachAxis(self.axis_x)
        self.main_series.attachAxis(self.axis_y)
        self.fix_series.attachAxis(self.axis_x)
        self.fix_series.attachAxis(self.axis_y)
        
        # 创建图表视图
        self.chart_view = QChartView(self.chart)
        self.chart_view.setRenderHint(QPainter.Antialiasing)
        
        # 启用交互功能
        self.chart_view.setInteractive(True)
        self.chart_view.setDragMode(QGraphicsView.ScrollHandDrag)  # 启用拖拽平移
        
        # 禁用默认的矩形缩放
        self.chart_view.setRubberBand(QChartView.NoRubberBand)
        
        # 安装事件过滤器实现滚轮缩放
        self.chart_view.viewport().installEventFilter(self)
        
        # 添加到主布局
        main_layout.addWidget(self.chart_view, 1)
    
    # 新增事件过滤器方法
    def eventFilter(self, source, event):
        """处理鼠标滚轮缩放事件"""
        if event.type() == event.Wheel and source is self.chart_view.viewport():
            # 获取当前视图的变换矩阵
            transform = self.chart_view.transform()
            
            # 计算当前缩放比例
            current_scale = transform.m11()  # 获取水平缩放比例
            
            # 如果是缩小操作且当前已经是原始比例(1.0)或更小，则阻止继续缩小
            if event.angleDelta().y() < 0 and current_scale <= 1.0:  # 1.0是原始比例
                return True
            
            # 获取鼠标当前位置的图表坐标
            mouse_pos = event.pos()
            chart_pos = self.chart_view.mapToScene(mouse_pos)
            
            # 获取当前缩放因子，这个值决定缩放速度
            factor = 1.2 if event.angleDelta().y() > 0 else 1/1.2    
            
            # 执行缩放
            self.chart_view.scale(factor, factor)
            
            # 调整视图中心点，实现以鼠标位置为中心的缩放
            view_center = self.chart_view.mapToScene(self.chart_view.viewport().rect().center())
            mouse_pos_after = self.chart_view.mapToScene(mouse_pos)
            move = mouse_pos_after - chart_pos
            self.chart_view.centerOn(view_center - move)
            
            return True
        
        return super().eventFilter(source, event)

    def setup_connections(self):
        """连接所有信号与槽"""
        # 文件操作
        self.actions['import_action'].triggered.connect(self.handle_import)
        self.actions['import_multibeam_action'].triggered.connect(self.handle_import_multibeam)    
        
        # 视图操作
        self.actions['original_plot_action'].triggered.connect(self.handle_original_plot)
        
        # 导航操作
        self.actions['prev_section_action'].triggered.connect(self.handle_prev_section)
        self.actions['next_section_action'].triggered.connect(self.handle_next_section)
        
        # 工具栏连接
        self.prev_btn.clicked.connect(self.handle_prev_section)
        self.next_btn.clicked.connect(self.handle_next_section)

        self.section_combo.currentIndexChanged.connect(self.handle_section_change)
        
        # 添加输出功能连接
        self.actions['live_output_action'].triggered.connect(self.handle_live_output)
        self.actions['raw_output_action'].triggered.connect(self.handle_raw_output)

    def handle_live_output(self):
        """处理实况输出"""
        try:
            from PyQt5.QtWidgets import QMessageBox
            # 调用controller的export_zzfix方法
            output_file = self.controller.export_zzfix()
            # 显示成功消息
            QMessageBox.information(self, "成功", f"实况输出成功:\n{output_file}")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"实况输出失败:\n{str(e)}")

    def handle_raw_output(self):
        """处理原始输出"""
        try:
            from PyQt5.QtWidgets import QMessageBox
            # 调用controller的ys_export_zzfix方法
            output_file = self.controller.ys_export_zzfix()
            # 显示成功消息
            QMessageBox.information(self, "成功", f"原始输出成功:\n{output_file}")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"原始输出失败:\n{str(e)}")
     
    def handle_original_plot(self):
        """处理原画复现"""
        self.controller.ytfxbj()
        self.update_chart_from_config()

    def handle_prev_section(self):
        """处理上一断面"""
        self.controller.dmdec()
        self.update_chart_from_config()

    def handle_next_section(self):
        """处理下一断面"""
        self.controller.dminc()
        self.update_chart_from_config()


    def handle_section_change(self, index):
        """处理断面选择变化"""
        self.controller.select_section(index)
        self.update_chart_from_config()



 
    def handle_import(self):
        """处理文件导入 - 保持与原代码一致的路径处理逻辑"""
        try:
            from PyQt5.QtWidgets import QFileDialog, QProgressDialog, QMessageBox
            from PyQt5.QtCore import Qt
            import os
            
            # 选择文件 - 与原代码一致的文件类型过滤
            file_in, _ = QFileDialog.getOpenFileName(
                self, 
                "选择数据文件", 
                "", 
                "LOG文件 (*.LOG)"
            )
            
            if not file_in:
                return
                
            # 添加调试信息
            print(f"开始导入文件: {file_in}")
            
            # 初始化数据 - 完全按照原代码的处理方式
            filepath, tempfilename = os.path.split(file_in)
            filepath = filepath.replace('/', '\\')  # 与原代码一致的处理
            
            self.controller.config.notesection = []
            self.controller.config.notemenu = []
            
            # 读取文件并处理 - 采用与原代码完全一致的路径处理逻辑
            with open(file_in, 'r') as f:
                for line in f:
                    line = line.strip('\n')
                    
                    # 定位文件名中的位置信息，作为日志文件名的一部分
                    e = line.rfind('\\')
                    fi = line[e+1:] if e != -1 else line
                    
                    # 与原代码完全一致的路径拼接方式
                    full_path = filepath + '\\' + fi
                    
                    # 检查文件是否存在
                    if not os.path.exists(full_path):
                        print(f"文件不存在: {full_path}")
                        continue
                        
                    self.controller.config.notesection.append(fi)
                    self.controller.config.notemenu.append(full_path)
            
            # 在批量处理前弹窗设置点距
            original_scrpoint = self.controller.config.scrpoint
            scrpoint_restore_needed = False
            choice_box = QMessageBox(self)
            choice_box.setWindowTitle("点距筛选")
            choice_box.setText(f"是否按点距筛选FIX点？\n当前点距: {self.controller.config.scrpoint}\n选择“否”保留原始FIX点（不筛选）。\n选择“是”可输入新的点距。")
            yes_btn = choice_box.addButton("是", QMessageBox.YesRole)
            no_btn = choice_box.addButton("否", QMessageBox.NoRole)
            choice_box.exec_()
            if choice_box.clickedButton() == yes_btn:
                # 输入新点距
                value, ok = QInputDialog.getDouble(self, "设置点距", "请输入点距（米）:", 20.0, 0.001, 999999.0, 3)
                if ok:
                    try:
                        if value <= 0:
                            raise ValueError("点距必须大于0")
                        self.controller.config.scrpoint = value
                        self.controller.config.range_control = 0
                        scrpoint_restore_needed = False
                    except ValueError as e:
                        QMessageBox.warning(self, "输入错误", str(e))
                        # 若输入无效，临时不筛选，并在处理后恢复原值
                        self.controller.config.scrpoint = 0
                        scrpoint_restore_needed = True
                else:
                    # 取消输入则保留原始（不筛选），处理后恢复原值
                    self.controller.config.scrpoint = 0
                    scrpoint_restore_needed = True
            else:
                # 选择否：不筛选，处理后恢复原值
                self.controller.config.scrpoint = 0
                scrpoint_restore_needed = True
            
            # 设置进度对话框（在确定点距后再开始）
            progress = QProgressDialog("正在处理数据...", "取消", 0, 100, self)
            progress.setWindowTitle("加载进度")
            progress.setWindowModality(Qt.WindowModal)
            progress.setAutoClose(True)
            progress.show()
            
            # 添加调试信息
            print(f"找到 {len(self.controller.config.notesection)} 个断面，点距={self.controller.config.scrpoint}")
            
            # 批量处理每个断面
            total_sections = len(self.controller.config.notesection)
            
            for i in range(total_sections):
                if progress.wasCanceled():
                    break
                # 确保ytfx长度足够
                if len(self.config.ytfx) <= i:
                    self.config.ytfx.append(0)
                self.controller.config.dmnum = i
                try:
                    self.controller.process_date()  # 调用控制器中的处理方法
                except Exception as e:
                    print(f"处理断面{i}失败: {e}")
                    pass
                    
                # 更新进度
                progress.setValue(int((i + 1) / total_sections * 100))
                QApplication.processEvents()  # 刷新UI
                
            # 处理完成后更新UI
            self.section_combo.clear()
            self.section_combo.addItems(self.controller.config.notesection)
            self.section_combo.setCurrentIndex(0)
            # 直接使用config中的数据绘制图表
            self.update_chart_from_config() 
            
            # 若选择不筛选或取消输入，恢复原scrpoint
            if scrpoint_restore_needed:
                self.controller.config.scrpoint = original_scrpoint
            
            # 关闭进度对话框
            progress.close()
        except:
            pass
    def handle_import_multibeam(self):
        """处理多波束文件导入"""
        try:
            from PyQt5.QtWidgets import QFileDialog, QMessageBox
            # 选择文件 - 与原代码一致的文件类型过滤
            file_in, _ = QFileDialog.getOpenFileName(
                self, 
                "选择多波束数据文件", 
                "", 
                "TIFF文件 (*.tif *.tiff)"
            )
            
            if not file_in:
                return
                
            # 调用控制器处理多波束导入
            self.controller.import_multibeam(file_in)
            QMessageBox.information(self, "成功", "多波束数据导入成功")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"多波束导入失败:\n{str(e)}")
    def update_chart_from_config(self):
        """直接从config更新图表数据 - 与原有matplotlib逻辑保持一致"""
        # 清除所有系列 - 只清除主线和修正线
        self.main_series.clear()
        self.fix_series.clear()
        
        # 清除文本标注 - 修改为通过场景删除
        scene = self.chart_view.scene()
        for text in self.text_annotations:
            if text.scene() is not None:  # 检查文本是否仍在场景中
                scene.removeItem(text)
        self.text_annotations.clear()
        
        current_index = self.controller.config.dmnum
        
        
        # 安全检查
        if (current_index < 0 or 
            current_index >= len(self.controller.config.notemenu) or
            not hasattr(self.controller.config, 'dfEC1') or
            len(self.controller.config.dfEC1) <= current_index):
            print("⚠️ 安全检查失败: 索引越界或dfEC1不存在")
            return
        
        try:
            # 打印主数据线信息
            if len(self.controller.config.EC1[current_index]) > 0:
                
                for time, depth in zip(self.controller.config.dfEC1[current_index]['时间'], 
                                    self.controller.config.dfEC1[current_index]['水深']):
                    self.main_series.append(time, depth)
            else:
                print("⚠️ 主数据线 (EC1) 为空")
            
            
            
            for time, depth in zip(self.controller.config.ZZFIX[current_index]['时间'],
                                self.controller.config.ZZFIX[current_index]['水深']):
                self.fix_series.append(time, depth)
                
            # 获取Y轴范围 - 新增，模拟matplotlib的get_ylim()
            y_min, y_max = self.calculate_y_range(current_index)
            
            # 获取X轴范围
            if len(self.controller.config.dfEC1[current_index]) > 0:
                x_min = min(self.controller.config.dfEC1[current_index]['时间'])
                x_max = max(self.controller.config.dfEC1[current_index]['时间'])
            else:
                x_min, x_max = 0, 1
            
            # 设置坐标轴范围
            self.axis_x.setRange(x_min, x_max)
            self.axis_y.setRange(y_min, y_max)
            
            # 添加序号和水深标签 - 新增，模拟matplotlib的annotate
            self.add_annotations(current_index, y_min)
            
            # 更新标题 - 保持原有逻辑
            title = self.controller.config.notemenu[current_index]
            if self.controller.config.ytfx[current_index] == 1:
                title += " (原图)"
            self.chart.setTitle(title)
            
        except Exception as e:
            print(f"❌ 更新图表时出错: {str(e)}")
            import traceback
            traceback.print_exc()
    def calculate_y_range(self, current_index):
        """计算Y轴范围，与matplotlib的get_ylim()类似"""
        # 获取所有水深数据
        all_depths = []
        
        if len(self.controller.config.EC1[current_index]) > 0:
            all_depths.extend(self.controller.config.dfEC1[current_index]['水深'].tolist())
        
        all_depths.extend(self.controller.config.ZZFIX[current_index]['水深'].tolist())
        
        if not all_depths:
            return 0, 1  # 默认范围
        
        y_min = min(all_depths)
        y_max = max(all_depths)
        
        # 添加一些边距
        y_range = y_max - y_min
        if y_range == 0:
            y_range = 1  # 防止除零
        
        return y_min - 0.1 * y_range, y_max + 0.1 * y_range

    def add_annotations(self, current_index, y_min):
        """添加序号和水深标签，模拟matplotlib的annotate功能"""
        try:
            zzfix_data = self.controller.config.ZZFIX[current_index]
            
            for i, (time, depth, seq) in enumerate(zip(zzfix_data['时间'], 
                                                    zzfix_data['水深'], 
                                                    zzfix_data['序号'])):
                # 添加序号标签 (底部)
                seq_text = QGraphicsTextItem(str(int(seq)))
                seq_text.setPos(time, y_min)
                seq_text.setDefaultTextColor(Qt.black)
                font = seq_text.font()
                font.setBold(True)
                font.setPointSize(12)
                seq_text.setFont(font)
                self.chart.scene().addItem(seq_text)
                self.text_annotations.append(seq_text)
                
                # 添加水深标签 (数据点旁)
                depth_text = QGraphicsTextItem(str(depth))
                depth_text.setPos(time, depth)
                depth_text.setDefaultTextColor(Qt.black)
                font = depth_text.font()
                font.setBold(True)
                font.setPointSize(14)
                depth_text.setFont(font)
                self.chart.scene().addItem(depth_text)
                self.text_annotations.append(depth_text)
                
        except Exception as e:
            print(f"添加标注时出错: {str(e)}")
            import traceback
            traceback.print_exc()

    def plot_all_sections_2d(self):
        """绘制所有断面的2D汇总图 - 将所有点绘制在同一个平面图上"""
        from PyQt5.QtWidgets import QDialog, QVBoxLayout
        from PyQt5.QtChart import QChart, QChartView, QScatterSeries, QValueAxis
        from PyQt5.QtGui import QPainter, QPen
        from PyQt5.QtCore import Qt
        import pandas as pd
        import numpy as np
        
        # 创建新的图表窗口
        dialog = QDialog(self)
        dialog.setWindowTitle('所有断面汇总图')
        dialog.resize(1000, 600)
        
        # 创建图表
        chart = QChart()
        chart.setTitle('所有断面点汇总平面图')
        
        # 使用QScatterSeries来专门绘制点，这样可以更好地控制点的样式
        all_points_series = QScatterSeries()
        all_points_series.setName("所有点")
        all_points_series.setMarkerSize(8)  # 设置点大小
        
        # 收集所有有效坐标点
        all_e = []
        all_n = []
        
        # 遍历所有断面
        for dmnum in range(len(self.controller.config.notemenu)):
            if dmnum in self.controller.config.ZZFIX and isinstance(self.controller.config.ZZFIX[dmnum], pd.DataFrame) and not self.controller.config.ZZFIX[dmnum].empty:
                section_data = self.controller.config.ZZFIX[dmnum]
                
                # 添加该断面的所有点到汇总系列
                for _, row in section_data.iterrows():
                    if 'E' in row and 'N' in row:
                        try:
                            e = float(row['E'])
                            n = float(row['N'])
                            all_points_series.append(e, n)  # 使用E和N坐标
                            all_e.append(e)
                            all_n.append(n)
                        except (ValueError, TypeError):
                            continue
        
        if not all_e:
            QMessageBox.information(self, "提示", "没有找到有效的坐标数据")
            return
        
        # 添加汇总系列到图表
        chart.addSeries(all_points_series)
        
        # 创建坐标轴
        axis_x = QValueAxis()
        axis_x.setTitleText("E坐标")
        axis_y = QValueAxis()
        axis_y.setTitleText("N坐标")
        
        chart.addAxis(axis_x, Qt.AlignBottom)
        chart.addAxis(axis_y, Qt.AlignLeft)
        
        # 附加系列到坐标轴
        all_points_series.attachAxis(axis_x)
        all_points_series.attachAxis(axis_y)
        
        # 计算坐标范围并设置等比例显示
        e_min, e_max = min(all_e), max(all_e)
        n_min, n_max = min(all_n), max(all_n)
        
        # 计算数据范围
        e_range = e_max - e_min
        n_range = n_max - n_min
        
        # 添加边距，避免点贴在坐标轴上
        margin =0
        
        # 设置坐标轴范围，确保X和Y轴比例相同
        axis_x.setRange(e_min - margin, e_max + margin)
        axis_y.setRange(n_min - margin, n_max + margin)
        
        # 设置坐标轴刻度单位相同，保持比例一致
        # 计算合适的刻度间隔（使用对数以适应不同范围的数据）
        max_range = max(e_range, n_range)
        tick_count = 10  # 大致的刻度数量
        tick_interval = max_range / tick_count
        
        # 调整为合适的数值（如1, 2, 5的倍数）
        magnitude = 10 ** np.floor(np.log10(tick_interval))
        normalized_interval = tick_interval / magnitude
        
        if normalized_interval <= 1:
            tick_interval = magnitude
        elif normalized_interval <= 2:
            tick_interval = 2 * magnitude
        elif normalized_interval <= 5:
            tick_interval = 5 * magnitude
        else:
            tick_interval = 10 * magnitude
        
        axis_x.setTickCount(int(e_range / tick_interval) + 2)
        axis_y.setTickCount(int(n_range / tick_interval) + 2)
        
        # 关键设置：确保QChart保持坐标轴比例
        chart.setAnimationOptions(QChart.NoAnimation)  # 禁用动画以避免比例变化
        
        # 创建图表视图
        chart_view = QChartView(chart)
        chart_view.setRenderHint(QPainter.Antialiasing)
        
        # 添加到布局
        layout = QVBoxLayout()
        layout.addWidget(chart_view)
        dialog.setLayout(layout)
        
        # 将对话框保存为实例变量，防止被垃圾回收
        self.chart_dialogs.append(dialog)
        
        # 使用show()而非exec_()来显示非模态窗口，这样就不会阻塞其他操作
        dialog.show()

    def plot_all_sections_3d(self):
        try:
            # 添加必要的导入
            import pandas as pd
            import matplotlib.pyplot as plt
            from matplotlib import cm
            import numpy as np
            from scipy.interpolate import griddata
            from scipy.spatial import cKDTree, Delaunay
            from scipy.ndimage import gaussian_filter
            # 导入PyQt5相关组件
            from PyQt5.QtWidgets import QDialog, QVBoxLayout
            from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
            from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

            # 设置matplotlib中文字体支持，解决中文乱码问题
            plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei", "SimSun"]
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

            # 检查数据是否已加载
            if not hasattr(self.controller.config, 'ZZFIX') or not self.controller.config.ZZFIX:
                QMessageBox.information(self, "提示", "请先加载数据")
                return

            all_e = []
            all_n = []
            all_depth = []

            # 遍历所有断面数据
            for dmnum in range(len(self.controller.config.notemenu)):
                if dmnum in self.controller.config.ZZFIX and isinstance(self.controller.config.ZZFIX[dmnum], pd.DataFrame) and not self.controller.config.ZZFIX[dmnum].empty:
                    section_data = self.controller.config.ZZFIX[dmnum]
                    
                    # 遍历DataFrame中的每一行
                    for _, row in section_data.iterrows():
                        try:
                            # 提取E, N和水深坐标
                            e = float(row['E'])
                            n = float(row['N'])
                            # 假设水深在'水深'列
                            depth = float(row.get('水深', 0))
                            
                            # 添加到数据列表
                            all_e.append(e)
                            all_n.append(n)
                            all_depth.append(depth)
                        except (ValueError, TypeError, IndexError):
                            # 跳过无效数据
                            continue

            if not all_e:
                QMessageBox.information(self, "提示", "没有找到有效的断面数据")
                return

            # 创建非模态对话框
            dialog = QDialog(self)
            dialog.setWindowTitle('水下地形3D曲面图')
            dialog.resize(1000, 800)

            # 创建3D图形
            fig = plt.figure(figsize=(12, 10))
            ax = fig.add_subplot(111, projection='3d')

            # 转换为numpy数组以便处理
            e_array = np.array(all_e)
            n_array = np.array(all_n)
            depth_array = np.array(all_depth)
            
            # 计算水深范围 - 确保在任何渲染模式下都能访问
            z_min, z_max = min(depth_array), max(depth_array)
            z_range = z_max - z_min
            
            # ===== 用户可直接修改的参数部分 =====
            # 渲染模式选择：'grid' 表示网格渲染，'tin' 表示三角网渲染
            render_mode = 'tin'  # 使用TIN模式，直接连接真实数据点
            
            # 数据点简化参数（已禁用）
            enable_point_simplification = False  # 禁用数据点简化
            max_points = 10000  # 仅为避免NameError而定义，不会实际使用
            
            # TIN渲染参数
            tin_line_width = 0.0  # 设置为0隐藏TIN线条
            tin_edge_color = 'none'  # 设置为'none'隐藏边缘颜色
            # 地形显示参数
            z_scale_factor = 1.0  # 使用原始Z轴比例，不进行缩放
            
            # 光照效果参数
            enable_lighting = False  # 禁用光照渲染
            # =================================
            
            # 数据点简化处理
            if enable_point_simplification and len(e_array) > max_points:
                # 随机采样到指定点数
                sample_indices = np.random.choice(len(e_array), max_points, replace=False)
                e_array = e_array[sample_indices]
                n_array = n_array[sample_indices]
                depth_array = depth_array[sample_indices]
                print(f"已简化数据点：从{len(all_e)}个点简化到{len(e_array)}个点")
                # 重新计算水深范围
                z_min, z_max = min(depth_array), max(depth_array)
                z_range = z_max - z_min
            
            # 计算数据范围和网格参数
            e_min, e_max = min(e_array), max(e_array)
            n_min, n_max = min(n_array), max(n_array)
            e_range = e_max - e_min
            n_range = n_max - n_min
            
            # 根据选择的渲染模式绘制3D图形
            if render_mode == 'tin':
            # ===== TIN（不规则三角网）渲染 =====
            # 创建Delaunay三角剖分
                try:
                    tri = Delaunay(np.column_stack((e_array, n_array)))
                    
                    # 应用Z轴缩放（保持原样）
                    scaled_depth = depth_array.copy()
                    scaled_depth = (scaled_depth - z_min) * z_scale_factor + z_min
                    
                    # 绘制三角网 - 添加一个小的linewidth以便观察TIN结构
                    surf = ax.plot_trisurf(e_array, n_array, scaled_depth, triangles=tri.simplices,
                                    cmap=cm.ocean, linewidth=tin_line_width, edgecolor=tin_edge_color,
                                    antialiased=True, alpha=1)
                    
                    # 同时绘制原始散点，便于对比
                    ax.scatter(e_array, n_array, scaled_depth, color='red', s=10, alpha=0.1)
                    
                    print("已使用TIN（不规则三角网）渲染模式")
                    print(f"数据覆盖范围：E[{e_min:.2f}, {e_max:.2f}]米, N[{n_min:.2f}, {n_max:.2f}]米")
                    print(f"水深范围：{z_min:.2f}米 到 {z_max:.2f}米")
                    print(f"Z轴缩放因子：{z_scale_factor}")
                    print(f"已添加原始散点用于对比")
                except Exception as e:
                    print(f"TIN渲染失败，切换到网格渲染: {str(e)}")
                    render_mode = 'grid'
                
            # 在render_mode == 'tin'分支后添加网格渲染实现
            elif render_mode == 'grid':
                # ===== 规则格网渲染 =====
                # 创建网格点
                grid_size = max(e_range, n_range) / 50  # 根据数据范围确定网格大小
                e_grid = np.arange(e_min, e_max, grid_size)
                n_grid = np.arange(n_min, n_max, grid_size)
                e_grid, n_grid = np.meshgrid(e_grid, n_grid)
                
                # 使用griddata进行插值
                depth_grid = griddata((e_array, n_array), depth_array, (e_grid, n_grid), method='cubic')
                
                # 应用Z轴缩放
                scaled_depth_grid = depth_grid.copy()
                if not np.isnan(scaled_depth_grid).all():
                    grid_z_min = np.nanmin(scaled_depth_grid)
                    scaled_depth_grid = (scaled_depth_grid - grid_z_min) * z_scale_factor + grid_z_min
                    
                    # 绘制网格表面
                    surf = ax.plot_surface(e_grid, n_grid, scaled_depth_grid, cmap=cm.ocean,
                                    linewidth=0, antialiased=True, alpha=0.9)
                    
                    print("已使用网格渲染模式")
                    print(f"网格大小: {len(e_grid)}x{len(n_grid)}")
                else:
                    print("网格渲染失败，切换到TIN渲染")
                   
            # 添加颜色条，显示水深与颜色的对应关系
            cbar = plt.colorbar(surf, ax=ax, pad=0.1)
            cbar.set_label('水深 (m)')

            # 设置坐标轴标签
            ax.set_xlabel('东坐标 (E)')
            ax.set_ylabel('北坐标 (N)')
            ax.set_zlabel('水深 (m)')

            # 设置标题
            ax.set_title('水下地形3D曲面图')

            # 调整视角，使地形看起来更立体
            ax.view_init(elev=90, azim=270)  # 调整仰角和方位角
            
            # 添加光照效果增强真实感
            if enable_lighting:
                # 设置3D图形的光照方向
                ax.texture_filled = True
                ax.set_autoscale_on(True)

            # 自动调整布局
            plt.tight_layout()

            # 创建matplotlib画布并添加到PyQt窗口
            canvas = FigureCanvas(fig)
            # 添加导航工具栏，方便用户缩放、平移等操作
            toolbar = NavigationToolbar(canvas, dialog)

            # 创建布局并添加组件
            layout = QVBoxLayout()
            layout.addWidget(toolbar)
            layout.addWidget(canvas)
            dialog.setLayout(layout)

            # 将对话框保存为实例变量，防止被垃圾回收
            self.chart_dialogs.append(dialog)

            # 使用show()而非exec_()来显示非模态窗口，这样就不会阻塞其他操作
            dialog.show()

        except Exception as e:
            import traceback
            traceback.print_exc()
            QMessageBox.critical(self, "错误", f"绘制3D图时出错: {str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # 显式创建所有依赖项
    config = AppConfig()
    processor = ProcessData(config)
    controller = MainController(config, processor)
    
    # 通过构造函数注入依赖
    main_ui = MainUI(config, processor, controller)
    main_ui.show()
    sys.exit(app.exec_())